/*
 * yocef - Lightweight IoC Event Framework
 *
 * Copyright (c) 2009, Yves Cuillerdier
 * All rights reserved.
 * 
 * This software is licensed under the new BSD License:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice, 
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the author nor the names of its contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * @version 0.8.5
 */
package yocef.client;

import java.util.Map;
import java.util.TreeMap;

/**
 * This class represents an Event.
 * <p>
 * Two different instance of this class cannot be <code>equal</code> (have the
 * same name).
 */
public final class Event implements Comparable<Event> {

	/* A Map to enforce single instance of a given Event type (name) */
	private static Map<String, Event> eventStock = new TreeMap<String, Event>();

	/* the name of the event */
	private final String eventName;

	/* Event constructor */
	private Event(final String name) {
		eventName = name;
	}

	/**
	 * Event Factory. Ensure that two distinct Event instances are not
	 * <code>equals</code>
	 * <p>
	 * Notice: This method is not synchronized
	 * 
	 * @param name
	 *            the name of the event
	 * @return the Event
	 */
	public static Event get(final String name) {
		// get Event from stock
		Event event = eventStock.get(name);
		if (event == null) {
			// Not already seen. Create it
			event = new Event(name);
			eventStock.put(name, event);
		}
		return event;
	}

	/**
	 * Bind this <tt>Event</tt> type with a source <tt>Object</tt>. The source
	 * never know about the event(s) and then could be any <tt>Object</tt>. In
	 * particular, it do not have to implement any <tt>addListener()</tt> or
	 * <tt>onEvent()</tt> methods.
	 * <p>
	 * However, if the <tt>Event</tt> is native to the <tt>Object</tt>, it will
	 * have to be "physically" binded with it by an {@link EventGlue}. Else
	 * {@link Event#fire(Object, EventParams)} will do the job for us.
	 * 
	 * @param element
	 *            the source element for the event.
	 * @return an EventBind, either a fresh one if no Binding are already
	 *         defined or a previously defined binding.
	 * @see EventBind
	 * @see EventBind#now()
	 * @see EventGlue
	 */
	public EventBind on(final Object element) {
		return EventBind.get(this, element);
	}

	/**
	 * Unbind the given element from this type of Event. From that moment, this
	 * event could no more be handle on the element
	 * 
	 * @param element
	 *            the element to unbind for this Event
	 */
	public void off(final Object element) {
		final EventBind bind = EventBind.get(this, element);
		if (bind != null) {
			bind.unlink();
		}
	}

	/**
	 * Fire this event on the given element. The {@link EventAction} defined for
	 * this <tt>{Event,Object}</tt> association is called.
	 * 
	 * @param element
	 *            the element to fire
	 * @param params
	 *            the parameters for the event
	 * @see EventAction
	 */
	public void fire(final Object element, final EventParams params) {
		final EventBind bind = EventBind.get(this, element);
		if (bind != null) {
			EventAction action = bind.getAction();
			if (action != null)
				bind.getAction().handle(this, element, bind.getContext(),
						params);
		}
	}

	/**
	 * Objects don't know anything about the events that are set on them. When
	 * an object is no more referred anywhere outside the event framework,
	 * references remaining in this framework prevent the garbage collector to
	 * reclaim the memory.
	 * <p>
	 * This problem could be solved:
	 * <ul>
	 * <li>using Java <b>WeakReference</b>: Weak References allow for really
	 * automatic cleaning at a very little cost. Unfortunately, this does not
	 * work for javascript (for example, this could prevent the use of GWT)</li>
	 * <li>calling a full <b>cleanup</b> method: When we know we do not need
	 * anymore to handle any kind of Event on a given object, we have to call a
	 * method that will remove all reference to the Object</li>
	 * </ul>
	 * <p>
	 * However, it is a good health practice to <tt>off</tt> an element as soon
	 * as we do not need to manage an event on it.
	 * 
	 * @param element
	 *            the element for witch all references need to be removed.
	 * @see Event#off(Object)
	 */
	public static void cleanup(Object element) {
		EventBind.cleanup(element);
	}

	/**
	 * Get the event name.
	 * 
	 * @return the eventName
	 */
	public String getName() {
		return eventName;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		return eventName.hashCode();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(final Object obj) {
		// Two distinct instances cannot be equals
		return (this == obj);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "Event '" + eventName + "'";
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Comparable#compareTo(java.lang.Object)
	 */
	public int compareTo(Event e) {
		return eventName.compareTo(e.eventName);
	}
}
